# -*- coding: utf-8 -*-
# Created by shu2015626 on 2019-10-24
"""
应用监控程序
"""

import os
import sys
import django

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, BASE_DIR)
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "PyTimedTasks.settings")
django.setup()

import time
import yaml
import logging
import traceback
from apps.utils.redis_oper import RedisOper
from apps.utils.email_oper import EmailOper
from django.conf import settings

PROJECT_FLAG = "PyTimedTasks"
logger = logging.getLogger("proj")


class AppMonitor(object):
    """
    应用监控
    """

    def __init__(self, cfg_monitors):
        self._obj_email_oper = EmailOper(settings.EMAIL_HOST, settings.EMAIL_PORT, settings.EMAIL_SENDER, settings.EMAIL_SENDER_PASS, settings.EMAIL_SENDER_ALIAS)
        self.__cfg_monitors = cfg_monitors

    def __load_config(self, cfg_filepath):
        """
        解析配置文件
        :return:
        """
        with open(cfg_filepath, 'rt') as f:
            cfg = yaml.safe_load(f)
        return cfg

    def monitor_heartbeat(self):
        """
        心跳监控：
        （1）用到redis,因此可以监控redis
        （2）用到celerybeat可以监控celerybeat
        （3）任务发到worker,可以监控一个worker是否堵塞
        （4）任务包括的celerybeat用到了数据库，也可以监控数据连接
        :return:
        """
        cfg_monitors = self.__load_config(self.__cfg_monitors)
        monitors = cfg_monitors.get('monitors', [])
        try:
            obj_redis_oper = RedisOper()
            heartbeat = obj_redis_oper.get_heartbeat()
            heartbeat = float(heartbeat) if heartbeat else None
            heartbeat_threshold = 60
            if (time.time() - heartbeat) > heartbeat_threshold:
                # 心跳是60s前发的，说明心跳任务失败
                msg = f'[{PROJECT_FLAG} Heartbeat 停止]心跳时间为：{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(heartbeat))}, ' \
                    f'当前时间为：{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}, ' \
                    f'已经超过{heartbeat_threshold}s没有更新。'
                subject = f'[{PROJECT_FLAG} Heartbeat 停止]心跳停止({time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())})'
                if monitors:
                    self._obj_email_oper.send_email(monitors, msg, subject,  'plain')
                else:
                    logger.error("[PyTimedTasks-心跳监控]没有读取到监控人员的配置信息")
        except Exception as e:
            msg = f'[{PROJECT_FLAG} Heartbeat 读取失败]读取心跳失败，当前时间为：{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}, 错误为：\n' + traceback.format_exc()
            subject = f'[{PROJECT_FLAG} Heartbeat 读取失败]读取心跳失败({time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())})'
            if monitors:
                self._obj_email_oper.send_email(monitors, msg, subject, 'plain')
            else:
                logger.error("[PyTimedTasks-心跳监控]没有读取到监控人员的配置信息")

    def monitor_db(self):
        """
        数据库监控：
        虽然monitor_heartbeat也可以监控数据库，但链路太长。可以单独实现监控数据库
        """
        sql = "select 1 from dual"
        pass


if __name__ == "__main__":

    obj_monitor = AppMonitor(settings.APP_MONITOR_CFG_FILEPATH)
    TASKS = {
        "monitor_heartbeat": {"interval": 10, "function": obj_monitor.monitor_heartbeat, "args": None, "kwargs": None},
    }

    import time
    from threading import Timer


    def map_tasks(task_name: str, tasks_mapping: dict = None):
        tasks_mapping = tasks_mapping or TASKS.copy()
        task_info = tasks_mapping.get(task_name, None)
        if task_info:
            t = Timer(**task_info)
            t.start()
            return t
        else:
            print("没有配置该任务")


    def emit(tasks_mapping: dict = None):
        tasks_mapping = tasks_mapping or TASKS.copy()
        running_tasks = {}
        for task_name in tasks_mapping.keys():
            running_tasks[task_name] = map_tasks(task_name, tasks_mapping)

        try:
            while True:
                time.sleep(1)
                for task_name in tasks_mapping.keys():
                    if not running_tasks[task_name].is_alive():
                        running_tasks[task_name] = map_tasks(task_name, tasks_mapping)
        except (KeyboardInterrupt, SystemExit):
            for task_name in tasks_mapping.keys():
                if running_tasks[task_name].is_alive():
                    running_tasks[task_name].cancel()
            print("exit")


    emit(TASKS.copy())


